﻿namespace IndicatorsTest.Utils
{
    using System;
    using System.Collections.Generic;
	using System.Linq;
	using Ecng.Common;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    using StockSharp.Algo.Indicators;

	static class TextDataRunner
    {
		public static void ByCandle(this IIndicator indicator, decimal epsilon = 0.000001m, int columnNumber = 1)
        {
			indicator.Check(data => data.Candle, epsilon, columnNumber);
        }

		public static void ByCandle(this IIndicator indicator, decimal epsilon, params int[] columnNumbers)
		{
			indicator.Check(data => data.Candle, epsilon, columnNumbers);
		}

		public static void ByClosePrice(this IIndicator indicator, decimal epsilon = 0.000001m, int columnNumber = 1)
		{
			indicator.Check(data => data.Candle.ClosePrice, epsilon, columnNumber);
		}

		public static void ByClosePrice(this IIndicator indicator, decimal epsilon, params int[] columnNumbers)
		{
			indicator.Check(data => data.Candle.ClosePrice, epsilon, columnNumbers);
		}

		private static void Check<T>(this IIndicator indicator, Func<TextData, T> getValue, decimal epsilon, params int[] columnNumbers)
		{
			if (indicator == null)
				throw new ArgumentNullException("indicator");

			if (getValue == null)
				throw new ArgumentNullException("getValue");

			var func = columnNumbers.Select(n =>
			{
				Func<TextData, decimal> f;
				switch (n)
				{
					case 1:
						f = item => item.Value;
						break;
					case 2:
						f =  item => item.Value2;
						break;
					case 3:
						f =  item => item.Value3;
						break;
					case 4:
						f =  item => item.Value4;
						break;
					default:
						throw new ArgumentException("columnNumber должен быть в интервале от 1 до 4");
				}
				return f;
			}).ToArray();

			var values = new List<TextData>();

			foreach (var item in TextDataReader.ReadFromFile(@"../../Data/" + indicator.Name + ".txt", ','))
			{
				values.Add(item);

				var value = indicator.Process(new TestIndicatorValue<T>(getValue(item)));

				if (value is ComplexIndicatorValue)
				{
					foreach (var pair in value.To<ComplexIndicatorValue>().InnerValues.Zip(func, (p1, p2) => new { Value = p1, Func = p2 }))
					{
						indicator.CheckValue(pair.Value.Value, values, pair.Func, epsilon);
					}
				}
				else
				{
					indicator.CheckValue(value, values, func[0], epsilon);
				}
			}

			// Провеяем что по окончании теста индикатор сформировался
			Assert.IsTrue(indicator.IsFormed);
		}

		private static void CheckValue(this IIndicator indicator, IIndicatorValue value, IList<TextData> values, Func<TextData, decimal> func, decimal epsilon)
		{
			if (!indicator.IsFormed)
				return;

			var shift = value is ShiftedIndicatorValue ? value.To<ShiftedIndicatorValue>().Shift : 0;

			try
			{
				Assert.IsTrue(Math.Abs(value.GetValue<decimal>() / func(values[values.Count - shift - 1]) - 1) < epsilon);
			}
			catch (DivideByZeroException)
			{
				Assert.IsTrue(Math.Abs(value.GetValue<decimal>()) < epsilon);
			}
		}

		public static IEnumerable<TResult> Zip<T1, T2, TResult>(this IEnumerable<T1> source1, IEnumerable<T2> source2, Func<T1, T2, TResult> func)
		{
			using (var iter1 = source1.GetEnumerator())
			using (var iter2 = source2.GetEnumerator())
			{
				while (iter1.MoveNext() && iter2.MoveNext())
				{
					yield return func(iter1.Current, iter2.Current);
				}
			}
		}
    }
}